#region Using Statements

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

#endregion


namespace Freeform
{
    /// <summary>
    /// This is the base class for your game.
    /// </summary>
    public class Game : Microsoft.Xna.Framework.Game
    {

        #region Members

        /// <summary>
        /// Gets the graphics device manager for this game.
        /// </summary>
        public GraphicsDeviceManager GraphicsDeviceManager { get; private set; }


        /// <summary>
        /// Gets or sets the spritebatch used to render the viewports to screen.
        /// </summary>
        public SpriteBatch SpriteBatch { get; private set; }


        /// <summary>
        /// Gets the list of viewports in this game.
        /// </summary>
        public List<Viewport2D> Viewports { get; private set; }


        /// <summary>
        /// Gets the list of worlds in this game.
        /// </summary>
        public List<World> Worlds { get; private set; }


        /// <summary>
        /// Gets or sets the background colour of the game.
        /// </summary>
        public virtual Color BackgroundColour { get; set; }


        /// <summary>
        /// Gets the random number generator used in this game.
        /// </summary>
        public Random Random { get; private set; }


        /// <summary>
        /// Gets a reference to the current game.
        /// </summary>
        public static Game GameRef { get; private set; }


        /// <summary>
        /// Gets an average frames per second value for the game.
        /// </summary>
        public Int32 FPS { get; private set; }


        /// <summary>
        /// Gets the fraction of CPU load used by the game.
        /// </summary>
        public Single CPULoad { get; private set; }


        /// <summary>
        /// Gets the components that are controlled directly by the game.
        /// </summary>
        public ComponentList ComponentList { get; private set; }


        /// <summary>
        /// Gets the input manager for this game.
        /// </summary>
        public InputManager Input { get; private set; }


        /// <summary>
        /// Gets the currently active RenderTarget for the Graphics Device.
        /// </summary>
        public RenderTarget2D ActiveTarget { get; private set; }


        /// <summary>
        /// Gets the KeyBinder which can be used to bind keys for the game.
        /// </summary>
        public KeyBinder KeyBinder { get; private set; }


        #region Private Implementation

        /// <summary>
        /// Gets a reference to a dictionary that can be used to lookup any Entity in the game via its ID.
        /// </summary>
        internal Dictionary<UInt64, Component> FreeformComponents { get; private set; }


        /// <summary>
        /// Counts the number of frames rendered each second.
        /// </summary>
        private Int32 Frames { get; set; }


        /// <summary>
        /// Counts down until the next FPS update.
        /// </summary>
        private Int32 NextFPSUpdate { get; set; }

        /// <summary>
        /// Stores the time when the Update() method begins.
        /// </summary>
        private DateTime Start { get; set; }


        /// <summary>
        /// Stores the time when the Draw() method ends.
        /// </summary>
        private DateTime Stop { get; set; }


        /// <summary>
        /// Stores the time span assosciated with the beginning of Update() and the end of Draw().
        /// </summary>
        private TimeSpan Running { get; set; }


        /// <summary>
        /// Stores the time spent idle between the end of Draw() and the beginning of Update().
        /// </summary>
        private TimeSpan Idle { get; set; }

        #endregion

        #endregion


        #region Init

        /// <summary>
        /// Creates a new freeform game instance.
        /// </summary>
        public Game(Vector2 resolution, Boolean fullScreen) : base() 
        {
            // Set game reference.
            GameRef = this;

            // Setup graphics device.
            GraphicsDeviceManager = new GraphicsDeviceManager(this);
            GraphicsDeviceManager.IsFullScreen = fullScreen;
            GraphicsDeviceManager.PreferredBackBufferWidth = (Int32)resolution.X;
            GraphicsDeviceManager.PreferredBackBufferHeight = (Int32)resolution.Y;
            
            // Setup content.
            Content.RootDirectory = "Content";

            // Create list of worlds.
            Worlds = new List<World>();

            // Create random number generator.
            Random = new Random();

            // Assign random starting point for IDs.
            Entity.NextID = (UInt64) Random.Base.Next();

            // Create entity dictionary.
            FreeformComponents = new Dictionary<UInt64, Component>();

            // Create component list.
            ComponentList = new ComponentList(this);

            // Create list of viewports.
            Viewports = new List<Viewport2D>();

            // Set background colour.
            BackgroundColour = Color.Black;

            // Create the input manager.
            Input = new InputManager(this);

            // Create the key binder.
            KeyBinder = new KeyBinder(this);
        }


        /// <summary>
        /// Allows the game to perform any initialisation before beginning to run.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            // Load sprite batch.
            SpriteBatch = new SpriteBatch(GraphicsDevice);

            Stop = DateTime.Now;
            NextFPSUpdate = 1000;
        }

        #endregion


        #region Update

        /// <summary>
        /// Allows the game to update itself.
        /// </summary>
        /// <param name="time">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime time)
        {
            Start = System.DateTime.Now;
            Idle = Start - Stop;

            base.Update(time);

            // Updates the component ordering and swaps in and out loaded / unloaded components.
            ComponentList.UpdateLists();

            // Allow each component to update itself.
            for (int i = 0; i < ComponentList.Loaded.Count; i++)
                if (ComponentList.Loaded[i].IsEnabled && !ComponentList.Loaded[i].Disposed)
                    ComponentList.Loaded[i].Update(time);
        }

        #endregion


        #region Draw

        /// <summary>
        /// Allows the game to draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime time)
        {
            // Allow each viewport to draw its scene.
            for (int i = 0; i < Viewports.Count; i++)
                if (Viewports[i].IsVisible && !Viewports[i].Disposed)
                    Viewports[i].Draw(time);

            // Reset render target to compile final scene.
            SetRenderTarget(null);

            // Clear screen.
            GraphicsDevice.Clear(BackgroundColour);

            // Begin compilation scene.
            SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);

            // Render each viewport to backbuffer.
            for (int i = 0; i < Viewports.Count; i++)
            {
                if (!Viewports[i].IsVisible || Viewports[i].Disposed) continue;

                // Calculate screen location for this viewport.
                Rectangle destination = new Rectangle((Int32)(Viewports[i].SpatialProperties.Position.X * GraphicsDeviceManager.PreferredBackBufferWidth),
                    (Int32)(Viewports[i].SpatialProperties.Position.Y * GraphicsDeviceManager.PreferredBackBufferHeight),
                    (Int32)(Viewports[i].SpatialProperties.Size.X * GraphicsDeviceManager.PreferredBackBufferWidth),
                    (Int32)(Viewports[i].SpatialProperties.Size.Y * GraphicsDeviceManager.PreferredBackBufferHeight));

                // Render viewport to screen.
                SpriteBatch.Draw(Viewports[i].RenderTarget, destination, null, Viewports[i].ColourOverlay, 0f, Vector2.Zero, SpriteEffects.None, Viewports[i].Depth);
            }

            // End scene.
            SpriteBatch.End();

            // Update FPS counter.
            Frames++;
            NextFPSUpdate -= time.ElapsedGameTime.Milliseconds;
            if (NextFPSUpdate < 0)
            {
                NextFPSUpdate += 1000;
                FPS = Frames;
                Frames = 0;
            }

            base.Draw(time);

            // Update CPU load
            Stop = System.DateTime.Now;
            Running = Stop - Start;
            CPULoad = (Single)Running.Ticks / (Single)(Running.Ticks + Idle.Ticks);
        }

        #endregion


        #region Utility

        /// <summary>
        /// Returns the component of the given ID.
        /// </summary>
        /// <param name="ID">The ID of the component we would like to fetch.</param>
        /// <returns>A freeform component or null if the ID does not exist.</returns>
        public virtual Component GetComponent(UInt64 ID)
        {
            Component Component;
            FreeformComponents.TryGetValue(ID, out Component);
            return Component;
        }


        /// <summary>
        /// Sets the RenderTarget for the graphics device.
        /// </summary>
        /// <param name="Target">The new Rendertarget for the graphics device.</param>
        public virtual void SetRenderTarget(RenderTarget2D Target)
        {
            ActiveTarget = Target;
            GraphicsDevice.SetRenderTarget(Target);
        }

        #endregion

    }
}
